Hito 3 Proyecto CC5206 Introducción a la Minería de Datos.

Identificación de Grafemarios en Mapuzungun.

Grupo 10: Cristian Ahumada, Diego Canales, Diego González.

Motivación y Objetivos principales

Existen 3 datos importantes sobre la siguación de las lenguas en el mundo en esta época [2]: -“El 97% de la población mundial habla aproximadamente un 4% de las lenguas del mundo” -“El 96% de las lenguas del mundo son habladas aproximadamente por un 3 % de los habitantes” -"Cerca del 90% de todas las lenguas podrían ser sustituidas por las dominantes de aquí a finales del Siglo XXI"

Y en el caso de este territorio, entre otras lenguas se tiene el Mapuzugun (o Mapudungun), que se habla principalmente en las regiones del sur, según el mismo informe de la Unesco, está en peligro.

Una de las herramientas que pueden ayudar a su revitalización, son las relacionadas con "Procesamiento del lenguaje natural" (o Natural language processing, NLP). Dentro de las mútliples aplicaciones que se tienen en esta área, están Análisis de Sentimiento, Reconocimiento de entidades, resumen de contenidos, etc, pero requieren como primer paso, una estandarización de la lengua o un corpus lo suficientemente grande para poder hacer un análisis en profunidad.

Hoy existen personas aprendiendo y enseñando este idioma, pero al ser una lengua oral en su incio y actualmente no ser una lengua estandarizada en su escritura, se tiene el problema de que existen diversos alfabetos o grafemarios para describir sus fonemas. Tres de los principales grafemarios son el Azümchefe, el Unificado y el Ragileo.
Esto afecta la enseñanza y el aprendizaje debido a que para poder leer la mayoría de los textos que existen en mapuzugun, se debe saber estos tres grafemarios como mínimo. A continuación se presenta los tres grafemarios del mapudungún comentados anteriormente.

Grafemarios

Descripción del Mapuzugun

El mapuzugun es una lengua aglutinante y polisintética, esto quiere decir que se compone de raices a las cuales se les van agregando sufijos distinguibles para formar frases. Esto hace que al ser de una estructura completamente diferente al castellano o al ingles, se tengan que buscar técnicas distintas de procesamiento de este idioma.

Un ejemplo de como funcionan es la siguiente tabla, la cual muestra como se escriben 5 palabras en mapuzugun que, independiente del grafemario, sonarían igual en el caso de ser escuchadas.

    | Traducción   |  Ragileo  |  Unificado  |  Azümchefe  |
    |--------------|-----------|-------------|-------------|
    | Lengua       |  kewvn    |  kewün      |  kewün      |
    | Zorro        |  gvrv     |  ngürü      |  gürü       |
    | Suegra       |  jaja     |  llalla     |  llalla     |
    | Ratón        |  zewv     |  dewü       |  zewü       |
    | Fuego        |  Kvxal    |  Kütral     |  Kütxal     |

En esta última tabla se puede notar que a la vista, las palabras en Ragileo para una persona, son mucho más distinguibles que las de Unificado y Azümchefe. Además una de sus particularidades es que cada fonema, tiene exactamente un solo caracter para describirlo, como en el caso de suegra, a diferencia de azümchefe y unificado.

Por lo tanto a partir de esta problemática y de las oportunidades que entregan las herramientas de machine learning, se propone la idea de hacer un identificador de grafemarios, que pueda servir de paso inicial para hacer una conversion entre grafemarios y que finalmente, avanzando hacia un procesamiento del mapuzugun sin la traba de la escritura.

Hipótesis

Teniendo en cuenta la motivación ya presentada, se propone la siguiente hipótesis: “Es posible, a partir de n-gramas de caracteres, identificar el grafemario de una frase en un texto en mapuzugun, entre los grafemarios Unificado, Azümchefe y Ragileo”. Esto con el fin normalizar la escritura del idioma, siendo una herramienta de aprendizaje y enseñanza del mapuzugun.

Datos

Exploración de Datos

El dataset completo está conformado por 36 textos de variadas temáticas, escritos en los tres grafemarios mencionados. Dichos textos se distribuyen de la siguiente manera:

    |               | Azümchefe |  Ragileo    | Unificado   |
    |---------------|-----------|-------------|-------------|
    | Textos        |       10  |          9  |         17  |
    | Palabras      |   36.075  |     29.836  |    240.549  |
    | Palabras      |   11.118  |      9.004  |     36.182  |
    | sin repetición|           |             |             |


En la exploración de los textos se logra notar que en algunos de ellos existen frases que corresponden a otros idiomas, o a palabras no pertenecientes a la lengua en estudio que están forzosamente en ella. Un ejemplo de lo primero son frases en español en un texto del ámbito político y un ejemplo de lo segundo son nombres de la biblia escritos en un determinado grafemario. Sin embargo, la cantidad de palabras problemáticas encontradas es menor frente al total de palabras de cada grafemario, por lo que se decide no quitarlas de los textos, pues no es un proceso fácilmente automatizable e implicaría que un conocedor del mapuzungun filtrara los textos a mano, lo cual sería costoso.

Preprocesamiento y limpieza de Datos

Como preprocesamiento de los datos, se procede a eliminar de los textos cualquier caracter que no sea útil para los experimentos que se pretende realizar, es decir, se limpian los números y símbolos que no forman parte de la lengua en estudio. Esto se logra utilizando el módulo 're' de Python, que permite trabajar con expresiones regulares. Además, todas las palabras son llevadas a letras minúsculas.

Además se hizo un análisis de stop words, del cual se desprende que hay un subconjunto de stop words, en las que éstas son independientes de grafemario. Esto porque tienen una frecuencia muy alta y por lo tanto influencia en la desición que tomará el clasificador. ['ñi','ti','tañi','pu','tati','kom','welu','fey','mew','am','reke','no','tayu','tamu','eymu',]. En los anexos es posible observar la función aplicada para la eliminación de stopwords, con la respectiva lista completa.

Para trabajar con los datos es útil crear un dataframe, por lo que se necesita una manera de representar las palabras de los textos. Se decide construir vectores descriptores a partir de n-gramas de caracteres, donde dados un n-grama y una palabra en particular, esta se describirá con 1's si contiene al n-grama y 0's si no. A continuación, se muestra un breve ejemplo usando bigramas para la frase 'minería de datos':

Bigramas: 'mi', 'in', 'ne', 'er', 'rí', 'ía', 'de', 'da', 'at', 'to', 'os'.

minería -> [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]

de -> [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]

datos -> [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1

El procedimiento para crear este dataframe es mostrado en el código presentado en el anexo, utilizando la representación recién explicada y agregando una nueva columna con la etiqueta correspondiente según cada grafemario.

Además es necesario mencionar que se dividió el dataset en una porción de training y testing, debido a que es necesario para un mayor fiabilidad del proceso. La proporción usada es de 80% training y 20% testing

Experimentos y Resultados preliminares

Los experimentos realizados en esta oportunidad consisten en la clasificación de los datos a partir del dataframe generado con los textos. Se utilizaron tres técnicas: clasificación con Naive Bayes, SVM y Redes Neuronales. En lo que sigue, se detalla acerca de estas técnicas:

Clasificación con descriptores a partir de n-gramas

Para este caso se construyeron los vectores descriptores de palabras a partir de bigramas y trigramas. Estos fueron probados con las tres técnicas mencionadas. En la siguiente imagen se pueden ver los bigramas y trigramas más frecuentes para los tres grafemarios:

Gráfico N-gramas más frecuentes según grafemario

Grafo

Los resultados obtenidos se muestran a continuación. En primer lugar se muestran las pruebas para Naive Bayes, luego las de SVM y finalmente la más relevante para redes neuronales, con sus respectivas métricas. Además se realizó una patición del dataset para poseer datos de training y testing, en una razon de 80% y 20% respectivamente, entregando los siguientes resultados, para un crossvalidation de 5 folds.

Naive Bayes

Como se comentó se utilizó Naive Bayes para bigramas y trigramas, esto nos da un resultado de:

    | Bigramas     | Azümchefe |  Unificado  |  Ragileo    |
    |--------------|-----------|-------------|-------------|
    | Precision    |     0.95  |       0.92  |       1.00  |
    | Recall       |     0.75  |       1.00  |       0.78  |
    | F1-Score     |     0.84  |       0.96  |       0.88  |


    | Trigramas    | Azümchefe |  Unificado  |  Ragileo    |
    |--------------|-----------|-------------|-------------|
    | Precision    |     0.90  |       0.93  |       0.95  |
    | Recall       |     0.72  |       1.00  |       0.76  |
    | F1-Score     |     0.78  |       0.95  |       0.88  |


Que muestra un claro mejor rendimiento de bigramas sobre trigramas.

Support Vector Machine

También se utilizó SVM con un kernel Lineal para bigramas y trigramas, esto nos da un resultado de:

    | Bigramas     | Azümchefe |  Unificado  |  Ragileo    |
    |--------------|-----------|-------------|-------------|
    | Precision    |     0.95  |      0.86   |       1.00  |
    | Recall       |     0.56  |      1.00   |       0.65  |
    | F1-Score     |     0.71  |      0.93   |       0.79  |


    | Trigramas    | Azümchefe |  Unificado  |  Ragileo    |
    |--------------|-----------|-------------|-------------|
    | Precision    |     0.94  |      0.87   |       0.96  |
    | Recall       |     0.56  |      0.98   |       0.65  |
    | F1-Score     |     0.70  |      0.94   |       0.77  |

Aquí, al igual que en Naive Bayes, se obtienen mejores resultados en bigramas que en trigramas.

Clasificación con Redes Neuronales

También se utilizó una red neuronal de capas oculas de la forma para bigramas y trigramas, para una red que, como se puede ver en la siguiente figura, tiene una arquitectura con capas ocultas de 100,50,100y 25 neuronas, además de las de incio y fin de la red.. que fue la que obtuvo el mejor rendimiento para las pruebas que se hicieron. Para llevar a cabo esta técnica, se utiliza la librería Scikit-Learn en Python, que permite trabajar con Redes Neuronales, utilizándose en este caso una red tipo Multilayer Perceptron o MLP. Este método usa el dataframe anteriormente presentado, el cual se particiona para testing y training en 20% y 80% respectivamente, con un cross-validation de 5 folds. Además se elige el parámetro de penalización(alpha) igual a 0.0001, tolerancia igual a 0.000000001, número de iteraciones igual a 500 y se utiliza el solver 'adam' presente en la librería. Presentando las métricas de precisión, recall y f1-score para cada caso:

arquitectura

| Bigramas     | Azümchefe |  Unificado  |  Ragileo    |
|--------------|-----------|-------------|-------------|
| Precision    |     0.79  |      0.87   |       0.72  |
| Recall       |     0.55  |      0.97   |       0.57  |
| F1-Score     |     0.65  |      0.92   |       0.64  |


| Trigramas    | Azümchefe |  Unificado  |  Ragileo    |
|--------------|-----------|-------------|-------------|
| Precision    |     0.76  |      0.85   |       0.71  |
| Recall       |     0.53  |      0.79   |       0.34  |
| F1-Score     |     0.80  |      0.83   |       0.27  |

En este caso el rendimiento baja, quedando este método peor evaluado para los datos actuales.

Análisis de resultados

Una vez presentados los resultados obtenidos para los tres experimentos, se puede afirmar lo siguiente:

La alta precisión obtenida en clasificación para el grafemario Ragileo puede debserse a que dicho grafemario presenta características que lo hacen fácilmente diferenciable del resto de los grafemarios, pues muestra menos características en común con el resto que lo que comparten Unificado con Azümchefe. Para notar esto, basta con mirar las tablas con ejemplos mostradas al comienzo del informe, donde por ejemplo, "Fuego" se escribe "Kvxal" en Ragileo, "Kütral" en Unificado y "Kütxal" en Azümchefe, apreciandose claras diferencias, como la utilización del grafema "v" en lugar de "ü", que es compartido por los otros dos grafemarios.

Para los tres experimentos, es decir, para la clasificación de palabras usando Naive Bayes, SVM y Redes Neuronales, se obtuvo un mejor desempeño al utilizar vectores descriptores generados a partir de bigramas por sobre los generados en base a trigramas. Lo anterior puede justificarse mediante la observación de los n-gramas más frecuentes, pues es posible verificar que existen más trigramas que bigramas en común entre los tres grafemarios. En el caso particular del top 5 graficado anteriormente, se observa que hay un único bigrama en común (el) y que los trigramas en común son más: (mew, fey, chi). Esto induce confusión en el proceso de clasificación, por lo que el desemepeño usando descriptores a base de trigramas es menor.

Por otra parte, el mejor desempeño se obtiene utilizando Naive Bayes, disminuye al utilizar SVM y finalmente la clasificación con Redes Neuronales entrega el peor desempeño de entre las opciones probadas en esta oportunidad. El hecho de que Naive Bayes haya mostrado un mejor desempeño que SVM puede deberse a que el primero calcula probabilidades de que una palabra pertenezca a un grafemario u otro, asumiendo independencia, mientras que SVM intenta separar geométricamente el espacio determinado por los vectores descriptores de palabras, lo cual no muestra una frontera clara según lo enunciado en el párrafo anterior. Además, una justificación posible para el bajo desempeño de Redes Neuronales es que dicho modelo, con la arquitectura utilizada para las pruebas, no se ajuste bien a la complejidad del modelo. Tal como se ha aprendido en clases, es usual que utilizar modelos demasiado complejos para problemas de clasificación que no son excesivamente complicados entregue malos resultados, pues el modelo acaba sobreajustandose a la data de training.

Conclusiones y futuras direcciones

Se verifica la hipótesis de la posibilidad de encontrar el grafemario de una palabra a partir de sus ngramas de caracteres, esto es debido a que cada alfabeto asocia distintos grafemas a sonidos iguales. Por lo tanto las diferencias se ven a nivel de caracteres.

Además, el hecho de haber usado en pricipio soluciones más simples, no solo resultó en un menor tiempo de computo sino que, además, mejoró los resultados obtenidos anteriormente por los clasificadores redes neuronales. Se logró bajar la dimensionalidad de los descriptores, pero se piensa que al ser tan grandes y al estar trabajando con palabras, se podría reducir aún más con el fin de descartar "la maldición de la dimensionalidad" y así poder hacer una mejor clasificación.

Si bien se quitaron stopwords, que no aportaban en clasificación debido a que se escribían igual en todos los grafemarios, quedaron algunas palabras con esas características dentro del dataset, y por lo tanto, como trabajo futuro se podría hacer un filtrado de estas palabras, además de las palabras que estén en castellano, que también hacen que los clasificadores tengan ruido extra al momento de aprender una palabra en mapuzugun, en caso de no querer filtrar las palabras en mapuzugun, se puede también mapuchizar todo lo que no esté en mapuzugun.

Otro trabajo futuro sería comparar este método con alguno que no use Machine Learning para detectar la forma de escritura, y por otro lado, revisar trabajos relacionados con identificación de idiomas cercanos o de la misma familia.

Sobre comentarios del Hito 2

Sobre los comentarios recibidos acerca del Hito 2, se acogió la sugerencia del cuerpo docente acerca de la complejidad de los métodos que se usaron inicialemente. Y se pudo observar que la consideración aportó de buena manera.

Referencias

[1] “Vitalidad y peligro de desaparición de las lenguas”. Unesco 2003. Disponible en: http://www.unesco.org/new/fileadmin/MULTIMEDIA/HQ/CLT/pdf/LVE_Spanish_EDITED%20FOR%20PUBLICATION.pdf

Anexos

- Creación del dataset

Generar un solo texto por grafemario.

In [1]:
# -*- coding: utf-8 -*-
"""
Created on Thu May 23 16:06:29 2019

@author: Diego
"""

import glob
import re
import os
path = os.getcwd()
fac=0
facr=0
files = glob.glob(path+'/textosgraf/*.txt')
newfile = open("todos.txt","a+")



for name in files:
    with open(name , 'r', encoding = "ISO-8859-1") as f:
        f = f.read().split()
        fac=fac+len(f)
        facr=facr+len(f)
        for word in f:
            word = re.sub(r'([^a-zA-ZüÜáéíóúÁÉÍÓÚñÑ\s]+)','',word).lower()
            newfile.write(word+' ')
print (fac)
print(files)
0
[]

Generar un texto general.

In [0]:
# -*- coding: utf-8 -*-

import glob
import re
import os
path = os.getcwd()
fac=0
facr=0
files = glob.glob(path+'/textosgraf/*.txt')
newfile = open("todos.txt","a+")



for name in files:
    with open(name , 'r', encoding = "ISO-8859-1") as f:
        f = f.read().split()
        fac=fac+len(f)
        facr=facr+len(f)
        #print(len(f))
        #f = list(set(f))
        for word in f:
            word = re.sub(r'([^a-zA-ZüÜáéíóúÁÉÍÓÚñÑ\s]+)','',word).lower()
            newfile.write(word+' ')
print (fac)
print(files)

Generar dataframe.

In [0]:
### -*- coding: utf-8 -*-


import csv
import pandas as pd 

text = open('todos.txt','r')
text = text.read().split()
n = 3
out = []
for word in text:
    ngrams = [word[i:i+n] for i in range(len(word)-n+1)]
    for ng in ngrams:
        if not ng in out:
            out.append(ng)

vectors = [[] for i in range(len(text))]

for i in range(len(text)):
    for ng in out:
        if ng in text[i]:
            vectors[i].append(1)
        else:
            vectors[i].append(0)

with open('todos.csv', 'w') as csvfile:
    writer = csv.writer(csvfile, dialect='excel', lineterminator='\n')
    writer.writerow(out)
    writer.writerows(vectors)



data = pd.read_csv('todos.csv', encoding = "ISO-8859-1")
graf = ['azvmcefe']*faca+ ['unificado']*facu+['ragileo']*facr
data = data.assign(grafemario = graf) 
data.to_csv('todos.csv', encoding = 'ISO-8859-1', index=False)

- Preprocesamiento

Código para eliminar las StopWords.

In [0]:
#Funcion encargada de elimnar las stopwords, sujetas a la lista kw
def kzugun(text):
    kw=['ñi','mew','ta','ka','fey','ti','tañi','pu','tati','kom','engün','egvn','egün',
 'egu,engu','welu','tüfachi','tvfaci','femngechi','femgeci','femgechi','chi','ci', 
 'iñche','iñce','tamün','tamvn','feymew','am','eymün','eymvn','reke','taiñ','tami',
 'chem','cem','tüfa','tvfa','feychi','feyci','müten','mvten','tachi','taci','doy',
 'zoy','no','feytichi','feytici','tayu','tamu','eymu','iñchiw','iñciw','iñchiñ','iñciñ']

    for i in kw:
        welu=re.compile('(\s+)('+i+')(\s+)|^('+i+')|('+i+')$')
        text=re.sub(welu,' ',text)
    return text

- Métodos

Código Naive Bayes

In [0]:
#Codigo que se encarga de aplicar el metodo de Naive Bayes

df = pd.DataFrame(data, columns = ['Palabra', 'Grafemario'])

X = df.Palabra
y = df.Grafemario
#Dividir el dataset en train y testing
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state = 42)



nb = Pipeline([('vect', CountVectorizer()),('tfidf', TfidfTransformer()),('clf', MultinomialNB()),])
nb.fit(X_train, y_train)

y_pred = nb.predict(X_test)
my_tags = ['az','un','ra']#Tags para cada grafemario

print('accuracy %s' % accuracy_score(y_pred, y_test))
print(classification_report(y_test, y_pred,target_names=my_tags))

#plt.figure(figsize=(10,4))
#df.grafemarios.value_counts().plot(kind='bar');

Código SVM

In [0]:
#Codigo que se encarga de aplicar el metodo de SVM


df = pd.DataFrame(data, columns = ['Palabra', 'Grafemario'])

X = df.Palabra
y = df.Grafemario

#Dividir el dataset en train y testing
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state = 42)


sgd = Pipeline([('vect', CountVectorizer()),('tfidf', TfidfTransformer()),('clf', SGDClassifier(loss='hinge', penalty='l2',alpha=1e-3, random_state=42, max_iter=5, tol=None)),])
sgd.fit(X_train, y_train)


y_pred = sgd.predict(X_test)

my_tags = ['az','ra','un']#Tags para cada grafemario

print('accuracy %s' % accuracy_score(y_pred, y_test))
print(classification_report(y_test, y_pred,target_names=my_tags))

Código Redes Neuronales

In [0]:
#Código para el método de redes neuronales
crg=[]
cmg=[]
mlp=MLPClassifier(hidden_layer_sizes=(100, 50,100,25,), max_iter=500, alpha=0.0001,
                     solver='adam', random_state=21,tol=0.000000001, verbose =1)
#Entrenar
mlp.fit(X_train,y_train)
#Predecir
pred = mlp.predict(X_test)
cr=classification_report(y_test, pred)
#Mostrar metricas de desempeño
print(cr)
crg.append(cr)
cm = confusion_matrix(y_test, pred)
print(cm)
cmg.append(cm)